#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>

#define ERR_FILE_FAILURE "We are sorry, but the input file could not be opened."
#define ERR_ALLOC "There was an error allocating memory."

#define INIT_CHOP 10
#define CHOP_RATIO 2
#define MAX_AUX_STRING 200

/* Global Variables */
int iflag = 0;
int opt;
char * buffer = NULL;
size_t buffer_alloc_size = INIT_CHOP;
size_t buffer_used_size = 0;

typedef enum{
	ERROR,
	OK
} t_status;

typedef enum{
	HELP_MODE,
	VERSION_MODE,
	STDIN_MODE,
	FILES_MODE,
	INVALID_ARGS
} mode;

/* Functions' Prototypes and Definitions */
t_status file_inverter(char *input[], size_t n_files);
t_status stdin_inverter(void);
t_status init_buffer(void);
t_status grow_buffer(void);

void print_help(void){
	fprintf(stdout, "Usage: tp0 [-hV] [file1] [file2] ... [fileN]\n");
}

void print_version(void){
	fprintf(stdout, "Version: tp0 Version 1.0 Build 15.09.2013\n");
}

void print_invalid_opt(void){
	fprintf(stderr, "Invalid Option\n");
}

mode parse_args(int argc, char *argv[]){
	mode program_mode;
	if (argc == 1){
		program_mode = STDIN_MODE;
	}else{
		program_mode  = FILES_MODE;
		while ((opt = getopt(argc, argv, "hV")) != -1){
			switch (opt){
			case 'h':
				program_mode  = HELP_MODE;
				break;
			case 'V':
				program_mode = VERSION_MODE;
				break;
			default:
				program_mode  = INVALID_ARGS;
			}
		}
	}
	return program_mode;
}

/* MAIN */
int main(int argc, char *argv[]){
	t_status status = OK;
	mode program_mode;
	program_mode = parse_args(argc, argv);

	if(init_buffer() == ERROR){
		fprintf(stderr, "%s\n", ERR_FILE_FAILURE);
		return EXIT_FAILURE;
	}
	switch (program_mode){
		case STDIN_MODE:
			status = stdin_inverter();
			break;
		case FILES_MODE:
			status = file_inverter(argv+1, argc-1); /* argv[1:] are filenames */
			break;
		case HELP_MODE:
			print_help();
			break;
		case VERSION_MODE:
			print_version();
			break;
		default:
			print_invalid_opt();
	}

	if(status == ERROR){
		fprintf(stderr, "%s\n", ERR_FILE_FAILURE);
		return EXIT_FAILURE;
	}
	if (buffer != NULL){
		free(buffer);
	}
	return EXIT_SUCCESS;
}

/* Functions' Definitions */
void invert_line(char *buffer, size_t size){
	char aux;
	size_t i;
	for(i = 0; i < (size / 2); ++i){
		aux = buffer[i];
		buffer[i] = buffer[size-i-1];
		buffer[size-i-1] = aux;
	}
}

t_status init_buffer(void){
	if(buffer == NULL) {
		if((buffer = malloc(INIT_CHOP)) == NULL)
			return ERROR;
	}
	return OK;
}

t_status grow_buffer(void){
	char *aux;
	if((aux = realloc(buffer, buffer_alloc_size * CHOP_RATIO)) == NULL){
		free(buffer);
		return ERROR;
	}
	buffer = aux;
	buffer_alloc_size = buffer_alloc_size * CHOP_RATIO;
	return OK;
}

t_status invert_file(FILE *in_file){
	int c;
	while((c = getc(in_file)) != EOF){				/* While Input File does not reach EOF */
		if(buffer_used_size == buffer_alloc_size - 1){			/* El -1 es por el '\0' */
			if (grow_buffer() != OK){
				return ERROR;
			}
		}
		if ((char) c == '\n'){
			if( buffer_used_size != 0){
				invert_line(buffer, buffer_used_size);
				buffer[buffer_used_size] = '\n';
				write(STDOUT_FILENO, buffer, buffer_used_size + 1);
				buffer_used_size = 0;
			}else{ /* It's an empty line */
				write(STDOUT_FILENO, "\n", 1);
			}
		}else{
			buffer[buffer_used_size++] = (char) c;
		}
	}
	/* If the last line of the file does not ends with a EOL buffer_used_size > 0 */
	if (buffer_used_size > 0){
		invert_line(buffer, buffer_used_size);
		write(STDOUT_FILENO, buffer, buffer_used_size);
	}
	return OK;
}

t_status file_inverter(char * files[], size_t n_files){
	FILE *in_file;
	size_t i;

	for(i = 0; i < n_files; ++i){
		/* We open the Source File */
		if((in_file = fopen(files[i], "r")) == NULL){
			return ERROR;
		}
		if(invert_file(in_file) != OK){
			return ERROR;
		}
		fclose(in_file);
	}
	return OK;
}

t_status stdin_inverter(){
	return invert_file(stdin);
}
